#include <sxFixedTimelineController.h>
#include <sxCanvas.h>
#include <sxReplayable.h>

const static unsigned int DEFUALT_START_TIME = 0u;
const static unsigned int DEFAULT_END_TIME = 100u;
const static unsigned int DEFAULT_DIVISION = 10;
const static float DEFAULT_SLIDER_SIZE = 0.1f;

Sx::Multimedia::Interface::FixedTimelineController::FixedTimelineController(Viewport* viewport, const std::shared_ptr<Canvas>& canvas, const std::shared_ptr<ScenePickingController>& pickingController, const std::shared_ptr<RendererImplementation>& cameraRenderer, const std::shared_ptr<RendererImplementation>& plotRenderer, const std::shared_ptr<RendererImplementation>& cuboidRenderer) : 
	PlotController(viewport, canvas, pickingController, cameraRenderer, plotRenderer) {

	this->slider = std::make_shared<Cuboid_Archetype>(cuboidRenderer, -0.1f, -1.0f, -0.1f, 0.1f, 1.0f, 0.1f);
	this->startTime = DEFUALT_START_TIME;
	this->currentTime = DEFUALT_START_TIME;
	this->endTime = DEFAULT_END_TIME;
	this->draggingSlider = false;
	this->gridSnap = true;
	this->sliderScale = DEFAULT_SLIDER_SIZE;
	this->divisions = DEFAULT_DIVISION;
	this->currentSliderPosition = 0.0f;
	this->replayable = nullptr;

	this->plot->getObject()->setDisplayGrid(Math::XZ, false);
	this->plot->getObject()->setDisplayGrid(Math::YZ, false);
	this->plot->getObject()->setDisplayZ(false);
	this->plot->getObject()->setTickLabelInterval(1);

	this->setDivisions(divisions);
	this->setSliderScale(this->sliderScale);
}

Sx::Multimedia::Interface::FixedTimelineController::FixedTimelineController(Viewport* viewport, const std::shared_ptr<Canvas>& canvas, const std::shared_ptr<ScenePickingController>& pickingController, const std::shared_ptr<RendererImplementation>& cameraRenderer, const std::shared_ptr<RendererImplementation>& plotRenderer, const std::shared_ptr<RendererImplementation>& cuboidRenderer, unsigned int nxTicks, unsigned int pxTicks, unsigned int nyTicks, unsigned int pyTicks, unsigned int nzTicks, unsigned int pzTicks, float spacing) :
	PlotController(viewport, canvas, pickingController, cameraRenderer, plotRenderer, nxTicks, pxTicks, nyTicks, pyTicks, nzTicks, pzTicks, spacing) {

	this->slider = std::make_shared<Cuboid_Archetype>(cuboidRenderer, -0.1f, -1.0f, -0.1f, 0.1f, 1.0f, 0.1f);
	this->startTime = DEFUALT_START_TIME;
	this->currentTime = DEFUALT_START_TIME;
	this->endTime = DEFAULT_END_TIME;
	this->draggingSlider = false;
	this->gridSnap = true;
	this->sliderScale = DEFAULT_SLIDER_SIZE;
	this->divisions = DEFAULT_DIVISION;
	this->currentSliderPosition = 0.0f;
	this->replayable = nullptr;

	this->plot->getObject()->setDisplayGrid(Math::XZ, false);
	this->plot->getObject()->setDisplayGrid(Math::YZ, false);
	this->plot->getObject()->setDisplayZ(false);
	this->plot->getObject()->setTickLabelInterval(1);

	this->setDivisions(divisions);
	this->setSliderScale(this->sliderScale);
}

Sx::Multimedia::Interface::FixedTimelineController::~FixedTimelineController() {}

bool Sx::Multimedia::Interface::FixedTimelineController::initialize() {
	bool success = PlotController::initialize();
	if ( this->slider != nullptr ) success |= this->slider->construct();
	return success;
}

void Sx::Multimedia::Interface::FixedTimelineController::render() const {
	PlotController::render();
	if ( this->slider != nullptr ) this->slider->render();

	if ( this->canvas != nullptr ) {
		if ( this->draggingSlider == true ) {
			this->canvas->fillRectangle(this->mousePosCur.x() + 20, this->mousePosCur.y(), 40, 18, Graphics::Color4f(0.6f, 0.7f, 0.9f, 0.8f));
			this->canvas->drawRectangle(this->mousePosCur.x() + 20, this->mousePosCur.y(), 40, 18, Graphics::Color4f(0.3f, 0.4f, 1.0f, 1.0f));
			this->canvas->drawText(String::ValueOf(static_cast<int>(this->currentTime)), this->mousePosCur.x() + 24, this->mousePosCur.y() + 13, Graphics::Color4f(0.0f, 0.0f, 0.0f, 1.0f));
		}
	}
}

void Sx::Multimedia::Interface::FixedTimelineController::setReplayable(const std::shared_ptr<Replayable>& replayable) {
	this->replayable = replayable;
}

void Sx::Multimedia::Interface::FixedTimelineController::mousePressEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	PlotController::mousePressEvent(x, y, button, modifierKey);
	if ( this->slider == nullptr ) return;

	Eigen::Vector3f pickStart, pickEnd, intPoint;
	this->createPickRay(x, y, pickStart, pickEnd);
	if ( slider->intersect(pickStart, pickEnd, intPoint) ) {
		this->slider->select();
		this->draggingSlider = true;
		this->inEdit = true;
	}
}

void Sx::Multimedia::Interface::FixedTimelineController::mouseReleaseEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	PlotController::mouseReleaseEvent(x, y, button, modifierKey);
	if ( this->slider == nullptr ) return;

	this->slider->deselect();
	this->draggingSlider = false;
	this->inEdit = false;
}

void Sx::Multimedia::Interface::FixedTimelineController::mouseClickEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	PlotController::mouseClickEvent(x, y, button, modifierKey);
}

void Sx::Multimedia::Interface::FixedTimelineController::mouseMoveEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	if ( this->slider == nullptr ) return;
	
	if ( this->draggingSlider == false ) {
		Eigen::Vector3f pickStart, pickEnd, intPoint;
		this->createPickRay(x, y, pickStart, pickEnd);
		this->slider->pick(pickStart, pickEnd, intPoint);

		PlotController::mouseMoveEvent(x, y, button, modifierKey);
	}
	else this->mouseMoveSlider(x, y, button, modifierKey);
}

void Sx::Multimedia::Interface::FixedTimelineController::mouseWheelEvent(int delta) {
	PlotController::mouseWheelEvent(delta);
	if ( this->slider == nullptr ) return;

	std::shared_ptr<Graphics::Archetypes::Objects::Camera_Archetype::ObjectType> camera = this->camera->getObject();
	if ( this->camera->getObject()->getProjection() == Math::ORTHOGRAPHIC ) {
		float zoom = camera->getOrthoZoom();
		float scale = (zoom * 0.1f);
		this->slider->setScale(scale, scale, scale);
	}	
}

bool Sx::Multimedia::Interface::FixedTimelineController::mouseMoveSlider(int x, int y, MouseButton button, ModifierKey modifierKey) {
	if ( this->slider == nullptr ) return false;
	if ( this->inputEnabled == false ) return false;

	this->inEdit = true;
	Eigen::Vector3f displacement = Eigen::Vector3f::Zero();

	std::shared_ptr<Graphics::Archetypes::Objects::Camera_Archetype::ObjectType> camera = this->camera->getObject();

	float moveX = this->positionDifference.x() * camera->getConX();
	float moveY = this->positionDifference.y() * camera->getConY();

	if ( this->camera->getObject()->getProjection() == Math::ORTHOGRAPHIC ) {
		Math::View view = camera->getView();
		switch ( view ) {
		case Math::FRONT: displacement.x() = -1.0f * moveX; break;
		case Math::BACK: displacement.x() = moveX; break;
		case Math::TOP: displacement.x() = -1.0f * moveX; break;
		case Math::BOTTOM: displacement.x() = -1.0f * moveX; break;
		case Math::LEFT: displacement.z() = moveX; break;
		case Math::RIGHT: displacement.z() = -1.0f * moveX; break;
		};
	}
	else {
		std::cerr << "[FixedTimelineController:mouseMoveSlider] Error: Perspective camera slider movement not implemented." << std::endl;
		displacement = Eigen::Vector3f::Zero();
	}

	//--------------------------------------------------------------------------
	// Unproject the position of the mouse to determine where to move the time
	// slider.
	//--------------------------------------------------------------------------
	Eigen::Vector3f mousePos, dir;
	this->unproject(x, y, mousePos, dir);
	float newPos = this->currentSliderPosition + displacement.x();

	//--------------------------------------------------------------------------
	// Test the bounds to ensure that the slider stays on the defined timeline.
	//--------------------------------------------------------------------------
	if ( newPos < 0.0f ) {
		this->setCurrentFrame(0);
	}
	else if ( newPos > ((float)this->endTime / (float)this->divisions) ) {
		this->setCurrentFrame(this->endTime);
	}
	else {
		this->currentSliderPosition += displacement.x();
		float snapLeft = Math::Snap(this->snapSpacing, this->currentSliderPosition);
		float snapRight = Math::Snap(this->snapSpacing, this->currentSliderPosition);

		if ( mousePos.x() >= snapRight ) this->slider->setPositionX(snapRight);
		if ( mousePos.x() <= snapLeft  ) this->slider->setPositionX(snapLeft);

		//--------------------------------------------------------------------------
		// This should work until the floating-point representation doesn't provide
		// enough precision.
		//--------------------------------------------------------------------------
		this->currentTime = static_cast<unsigned int>(Math::Round(this->slider->getPositionX() * this->divisions));
		if ( this->replayable != nullptr ) {
			if ( this->replayable.use_count() > 0 )
				this->replayable->setCurrentFrame(this->currentTime);
		}
	}
	return true;
}

bool Sx::Multimedia::Interface::FixedTimelineController::setCurrentTime(unsigned int time) {
	if ( this->slider == nullptr ) return false;
	if ( time < this->startTime ) {
		std::cerr << "[FixedTimelineController:setCurrentTime] Error: Time index out of bounds: " << time << std::endl;
		std::cerr << "  Time-line start: " << this->startTime << " end: " << this->endTime << std::endl;
		return false;
	}

	if ( time >= this->endTime ) {
		this->endTime = time;
	}

	float pos = static_cast<float>(time) / static_cast<float>(this->divisions);
	this->currentSliderPosition = pos;
	this->currentTime = time;
	return this->slider->setPositionX(pos);
}

bool Sx::Multimedia::Interface::FixedTimelineController::incrementTime() {
	return this->setCurrentTime(this->currentTime + 1);
}

bool Sx::Multimedia::Interface::FixedTimelineController::decrementTime() {
	return this->setCurrentTime(this->currentTime - 1);
}

bool Sx::Multimedia::Interface::FixedTimelineController::setCurrentFrame(unsigned int frame) {
	return this->setCurrentTime(frame);
}

bool Sx::Multimedia::Interface::FixedTimelineController::setEndTime(unsigned int endTime) {
	if ( endTime <= this->startTime ) return false;
	if ( this->plot == nullptr ) return false;
	if ( this->plot->getObject() == nullptr ) return false;
	this->endTime = endTime;

	this->plot->getObject()->setXTicks(static_cast<unsigned int>(std::ceil(static_cast<float>(this->endTime) / this->divisions)), true);
	this->plot->construct();

	return true;
}

bool Sx::Multimedia::Interface::FixedTimelineController::setFrameCount(unsigned int frameCount) {
	return this->setEndTime(frameCount);
}

unsigned int Sx::Multimedia::Interface::FixedTimelineController::getCurrentTime() const {
	return this->currentTime;
}

unsigned int Sx::Multimedia::Interface::FixedTimelineController::getStartTime() const {
	return this->startTime;
}

unsigned int Sx::Multimedia::Interface::FixedTimelineController::getEndTime() const {
	return this->endTime;
}

unsigned int Sx::Multimedia::Interface::FixedTimelineController::size() const {
	return (this->endTime - this->startTime);
}

bool Sx::Multimedia::Interface::FixedTimelineController::setDivisions(unsigned int count) {
	if ( count == 0 ) this->divisions = 1;
	else this->divisions = count;
	this->snapSpacing = (1.0f / this->divisions);
	return true;
}

bool Sx::Multimedia::Interface::FixedTimelineController::setSliderScale(float scale) {
	if ( this->slider == nullptr ) return false;
	if ( this->camera == nullptr ) return false;
	this->sliderScale = scale;

	float s = this->camera->getObject()->getOrthoZoom() * scale;
	return this->slider->setScale(s, s, s);
}
